home *** CD-ROM | disk | FTP | other *** search
- /*
- ** Part.m
- **
- ** Copyright (c) 2001, 2002
- **
- ** Author: Ludovic Marcotte <ludovic@Sophos.ca>
- **
- ** This library is free software; you can redistribute it and/or
- ** modify it under the terms of the GNU Lesser General Public
- ** License as published by the Free Software Foundation; either
- ** version 2.1 of the License, or (at your option) any later version.
- **
- ** This library is distributed in the hope that it will be useful,
- ** but WITHOUT ANY WARRANTY; without even the implied warranty of
- ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- ** Lesser General Public License for more details.
- **
- ** You should have received a copy of the GNU Lesser General Public
- ** License along with this library; if not, write to the Free Software
- ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
- #import <Pantomime/Part.h>
-
- #import <Pantomime/Constants.h>
- #import <Pantomime/NSStringExtensions.h>
- #import <Pantomime/NSDataExtensions.h>
- #import <Pantomime/MimeUtility.h>
- #import <Pantomime/MimeBodyPart.h>
- #import <Pantomime/MimeMultipart.h>
- #import <Pantomime/Message.h>
-
- static int currentVersion = 1;
-
- @implementation Part
-
- //
- //
- //
- - (id) init
- {
- self = [super init];
-
- [Part setVersion: currentVersion];
-
- // We set the default values
- [self setContentType: @"text/plain"];
- [self setContentTransferEncoding: NONE];
- [self setCharset: @"us-ascii"];
- [self setFormat: FORMAT_UNKNOWN];
- [self setLineLength: 0];
-
- return self;
- }
-
-
- //
- //
- //
- - (void) dealloc
- {
- //NSDebugLog(@"Part: -dealloc");
-
- TEST_RELEASE(content);
-
- RELEASE(contentType);
- RELEASE(contentID);
- RELEASE(contentDescription);
- RELEASE(contentDisposition);
- RELEASE(filename);
- RELEASE(boundary);
- RELEASE(protocol);
- RELEASE(charset);
-
- TEST_RELEASE(defaultCharset);
-
- [super dealloc];
- }
-
-
- //
- //
- //
- - (id) initWithData: (NSData *) theData
- {
- [Part setVersion: currentVersion];
-
- [self subclassResponsibility: _cmd];
- return nil;
- }
-
-
- //
- //
- //
- - (id) initWithData: (NSData *) theData
- charset: (NSString *) theCharset
- {
- [Part setVersion: currentVersion];
-
- [self subclassResponsibility: _cmd];
- return nil;
- }
-
-
- //
- // NSCoding protocol
- //
- - (void) encodeWithCoder: (NSCoder *) theCoder
- {
- [Part setVersion: currentVersion];
-
- [theCoder encodeObject: [self contentType]];
- [theCoder encodeObject: [self contentID]];
- [theCoder encodeObject: [self contentDescription]];
- [theCoder encodeObject: [self contentDisposition]];
- [theCoder encodeObject: [self filename]];
-
- [theCoder encodeObject: [NSNumber numberWithInt: [self contentTransferEncoding]]];
- [theCoder encodeObject: [NSNumber numberWithInt: [self format]]];
- [theCoder encodeObject: [NSNumber numberWithInt: [self size]]];
-
- [theCoder encodeObject: [self boundary]];
- [theCoder encodeObject: [self charset]];
- }
-
- - (id) initWithCoder: (NSCoder *) theCoder
- {
- int version;
-
- version = [theCoder versionForClassName: NSStringFromClass([self class])];
-
- self = [super init];
-
- [self setContentType: [theCoder decodeObject]];
- [self setContentID: [theCoder decodeObject]];
- [self setContentDescription: [theCoder decodeObject]];
- [self setContentDisposition: [theCoder decodeObject]];
- [self setFilename: [theCoder decodeObject]];
-
- [self setContentTransferEncoding: [[theCoder decodeObject] intValue]];
- [self setFormat: [[theCoder decodeObject] intValue]];
- [self setSize: [[theCoder decodeObject] intValue]];
-
- // Old version, we were using a NSString object
- if ( version == 1 )
- {
- [self setBoundary: [theCoder decodeObject]];
- }
- // We now use a NSData object
- else
- {
- id obj;
-
- obj = [theCoder decodeObject];
-
- if ( [obj isKindOfClass: [NSString class]] )
- {
- [self setBoundary: [obj dataUsingEncoding: NSASCIIStringEncoding]];
- }
- else
- {
- [self setBoundary: obj];
- }
- }
-
- [self setCharset: [theCoder decodeObject]];
- [self setDefaultCharset: nil];
-
- return self;
- }
-
-
-
- //
- // access / mutation methods
- //
-
- //
- //
- //
- - (NSObject *) content
- {
- return content;
- }
-
-
- //
- //
- //
- - (void) setContent: (NSObject *) theContent
- {
- RETAIN(theContent);
- RELEASE(content);
- content = theContent;
- }
-
- - (NSString *) contentType
- {
- return contentType;
- }
-
- - (void) setContentType: (NSString*) theContentType
- {
- RETAIN(theContentType);
- RELEASE(contentType);
- contentType = theContentType;
- }
-
-
- - (NSString *) contentID
- {
- return contentID;
- }
-
- - (void) setContentID: (NSString *) theContentID
- {
- RETAIN(theContentID);
- RELEASE(contentID);
- contentID = theContentID;
- }
-
-
- - (NSString *) contentDescription
- {
- return contentDescription;
- }
-
- - (void) setContentDescription: (NSString*)theContentDescription
- {
- RETAIN(theContentDescription);
- RELEASE(contentDescription);
- contentDescription = theContentDescription;
- }
-
- - (NSString *) contentDisposition
- {
- return contentDisposition;
- }
-
- - (void) setContentDisposition: (NSString *) theContentDisposition
- {
- RETAIN(theContentDisposition);
- RELEASE(contentDisposition);
- contentDisposition = theContentDisposition;
- }
-
- - (int) contentTransferEncoding
- {
- return contentTransferEncoding;
- }
-
- - (void) setContentTransferEncoding: (int) theEncoding
- {
- contentTransferEncoding = theEncoding;
- }
-
- - (NSString *) filename
- {
- return filename;
- }
-
- - (void) setFilename: (NSString *) theFilename
- {
- if ( theFilename && ([theFilename length] > 0) )
- {
- RETAIN(theFilename);
- RELEASE(filename);
- filename = theFilename;
- }
- else
- {
- RELEASE(filename);
- filename = @"unknown";
- RETAIN(filename);
- }
- }
-
-
- - (int) format
- {
- return format;
- }
-
- - (void) setFormat: (int) theFormat
- {
- format = theFormat;
- }
-
- - (int) lineLength
- {
- return line_length;
- }
-
- - (void) setLineLength: (int) theLineLength
- {
- line_length = theLineLength;
- }
-
-
- //
- // This method is used to very if the part is of the following primaryType / subType
- //
- - (BOOL) isMimeType: (NSString *) primaryType : (NSString *) subType
- {
- NSString *str;
-
- /* If the message has not content-type, we treat it as text/plain by adding it*/
- if (! [self contentType] )
- {
- [self setContentType: @"text/plain"];
- }
-
- if ( [subType compare: @"*"] == NSOrderedSame )
- {
- str = [self contentType];
-
- if ( [[self contentType] hasCaseInsensitivePrefix: primaryType] )
- {
- return YES;
- }
- }
- else
- {
- str = [NSString stringWithFormat:@"%@/%@", primaryType, subType];
- if ( [str caseInsensitiveCompare:[self contentType]] == NSOrderedSame) return YES;
- }
- return NO;
- }
-
-
- - (long) size
- {
- return size;
- }
-
- - (void) setSize: (long) theSize
- {
- size = theSize;
- }
-
-
- //
- //
- //
- - (NSData *) dataUsingSendingMode : (int) theMode
- {
- NSMutableData *aMutableData;
- NSData *aDataToSend;
-
- NSArray *anArray;
- int i;
-
- char *lineTerminator;
-
- if ( theMode == SEND_USING_SMTP )
- {
- lineTerminator = CRLF;
- }
- else
- {
- lineTerminator = LF;
- }
-
- aMutableData = [[NSMutableData alloc] init];
-
- // FIXME: Why is this acting differently depending on the content-type?
- // easier to just encode according to content-transfer-encoding:, split to
- // lines and add to the output
- if ( [self contentTransferEncoding] != NONE )
- {
- [aMutableData appendCFormat: @"Content-Transfer-Encoding: %@%s",
- [MimeUtility stringValueOfTransferEncoding: [self contentTransferEncoding]],
- lineTerminator];
- }
-
- if ( [self contentID] )
- {
- [aMutableData appendCFormat: @"Content-ID: %@%s", [self contentID], lineTerminator];
- }
-
- if ( [self contentDescription] )
- {
- [aMutableData appendCString: "Content-Description: "];
- [aMutableData appendData: [MimeUtility encodeWordUsingQuotedPrintable: [self contentDescription]
- prefixLength: 21]];
- [aMutableData appendCString: lineTerminator];
- }
-
- if ( [self isMimeType: @"multipart" : @"*"] )
- {
- if ([[self content] isKindOfClass: [MimeMultipart class]])
- {
- MimeMultipart *mp;
- MimeBodyPart *b;
- NSData *aBoundary;
- NSMutableData *md;
-
- md = [[NSMutableData alloc] init];
-
- aBoundary = [self boundary];
-
- if ( !aBoundary )
- {
- aBoundary = [MimeUtility generateBoundary];
- }
-
- [aMutableData appendCFormat: @"Content-Type: %@;%s", [self contentType], lineTerminator];
-
- if ( [self protocol] )
- {
- [aMutableData appendCString: "\tprotocol=\""];
- [aMutableData appendData: [self protocol]];
- [aMutableData appendCFormat: @"\";%s", lineTerminator];
- }
-
- [aMutableData appendCString: "\tboundary=\""];
- [aMutableData appendData: aBoundary];
- [aMutableData appendCFormat: @"\"%s", lineTerminator];
-
- mp = (MimeMultipart *)[self content];
-
- for (i = 0; i < [mp count]; i++)
- {
- b = [mp bodyPartAtIndex: i];
-
- if (i > 0)
- {
- [md appendBytes: lineTerminator length: strlen(lineTerminator)];
- }
-
- [md appendBytes: "--" length: 2];
- [md appendData: aBoundary];
- [md appendBytes: lineTerminator length: strlen(lineTerminator)];
-
- [md appendData: [b dataUsingSendingMode: theMode] ];
- }
-
- [md appendBytes: "--" length: 2];
- [md appendData: aBoundary];
- [md appendBytes: "--" length: 2];
- [md appendBytes: lineTerminator length: strlen(lineTerminator)];
-
- // FIXME - is that ok to autorelease it?
- // We never release aDataToSend anyway...
- aDataToSend = AUTORELEASE(md);
- }
- else
- {
- aDataToSend = (NSData *)[self content];
- }
- }
- else if ( [self isMimeType: @"text": @"*"] ||
- [self isMimeType: @"message" : @"delivery-status"] )
- {
- if ( [self isMimeType: @"text": @"plain"] && [self format] == FORMAT_FLOWED &&
- ([self contentTransferEncoding] == NONE || [self contentTransferEncoding] == EIGHTBIT) )
- {
- [aMutableData appendCFormat: @"Content-Type: %@; charset=\"%@\"; format=\"flowed\"%s",
- [self contentType], [self charset], lineTerminator];
- }
- else
- {
- // FIXME: check if charset= is a valid parameter for message/deliviry-status
- [aMutableData appendCFormat: @"Content-Type: %@; charset=\"%@\"%s",
- [self contentType], [self charset], lineTerminator];
- }
-
- if ( [[self content] isKindOfClass: [NSString class]] )
- {
- NSString *aString;
- int encoding;
-
- // FIXME - Should we do this if the content is a NSData or something else?
- if ( ([self contentTransferEncoding] == NONE || [self contentTransferEncoding] == EIGHTBIT) &&
- [self format] == FORMAT_FLOWED )
- {
- int limit;
-
- limit = [self lineLength];
-
- if (limit < 2 || limit > 998)
- {
- limit = 72;
- }
-
- aString = [MimeUtility unwrapPlainTextString: (NSString *)[self content]
- usingQuoteWrappingLimit: 998];
- aString = [MimeUtility wrapPlainTextString: aString
- usingWrappingLimit: limit];
- }
- else
- {
- aString = (NSString *)[self content];
- }
-
- // We get the right string encoding to convert the string object to a data object
- encoding = [MimeUtility stringEncodingForCharset: [[self charset] dataUsingEncoding: NSASCIIStringEncoding]];
-
- // FIXME - Should we allow lossy conversion?
- aDataToSend = [aString dataUsingEncoding: encoding
- allowLossyConversion: YES];
-
- }
- else if ([[self content] isKindOfClass: [NSData class]])
- {
- aDataToSend = (NSData *)[self content];
- }
- // If it isn't string or data it had better respond to this
- else
- {
- aDataToSend = [(Part *)[self content] dataUsingSendingMode: theMode];
- }
-
- //
- // If we had a Content-Disposition specified, let's add it.
- //
- if ( [self contentDisposition] )
- {
- // If it is an 'attachment', let's add the filename.
- if ( [[self contentDisposition] caseInsensitiveCompare: @"attachment"] == NSOrderedSame &&
- [self filename] )
- {
- NSString *aString;
-
- if ( [MimeUtility isASCIIString: [self filename]] )
- {
- aString = [self filename];
- }
- else
- {
- aString = [[NSString alloc] initWithData: [MimeUtility encodeWordUsingQuotedPrintable: [self filename]
- prefixLength: 0]
- encoding: NSASCIIStringEncoding];
- AUTORELEASE(aString);
- }
-
- [aMutableData appendCFormat: @"Content-Disposition: attachment; filename=\"%@\"%s",
- aString, lineTerminator];
- }
- else
- {
- [aMutableData appendCFormat: @"Content-Disposition: %@%s", [self contentDisposition], lineTerminator];
- }
- }
- }
- //
- // Our Content-Type is message/rfc822 or message/partial
- //
- else if ( [self isMimeType: @"message": @"rfc822"] ||
- [self isMimeType: @"message": @"partial"] )
- {
- [aMutableData appendCFormat: @"Content-Type: %@%s", [self contentType], lineTerminator];
-
- //
- // If we had a Content-Disposition specified, let's add it.
- //
- if ( [self contentDisposition] )
- {
- [aMutableData appendCFormat:@"Content-Disposition: %@%s", [self contentDisposition], lineTerminator];
- }
-
- if ( [[self content] isKindOfClass: [NSData class]] )
- {
- aDataToSend = (NSData *)[self content];
- }
- else
- {
- aDataToSend = [(Message *)[self content] rawSource];
- }
- }
- //
- // We surely have an application/*, audio/*, image/*,
- // video/* or anything else. We treat everything
- // like application/octet-stream
- //
- else
- {
- NSString *aString;
-
- if ( [MimeUtility isASCIIString: [self filename]] )
- {
- aString = [self filename];
- }
- else
- {
- aString = [[NSString alloc] initWithData: [MimeUtility encodeWordUsingQuotedPrintable: [self filename]
- prefixLength: 0]
- encoding: NSASCIIStringEncoding];
- AUTORELEASE(aString);
- }
-
- if ( aString )
- {
- [aMutableData appendCFormat: @"Content-Type: %@; name=\"%@\"%s", [self contentType], aString,
- lineTerminator];
-
- [aMutableData appendCFormat: @"Content-Disposition: attachment; filename=\"%@\"%s",
- aString, lineTerminator];
- }
- else
- {
- [aMutableData appendCFormat: @"Content-Type: %@%s", [self contentType], lineTerminator];
-
- }
-
- // FIXME: We could have a NSString as the content! We currently assume
- // its encoding is NSASCIIStringEncoding
- if ( [[self content] isKindOfClass: [NSString class]] )
- {
- aDataToSend = [(NSString *)[self content] dataUsingEncoding: NSASCIIStringEncoding
- allowLossyConversion: YES];
- }
- else
- {
- aDataToSend = (NSData *)[self content];
- }
- }
-
-
- // We separe our part's headers from the content
- [aMutableData appendCFormat: @"%s", lineTerminator];
-
- // We now encode our content the way it was specified
- if ( [self contentTransferEncoding] == QUOTEDPRINTABLE )
- {
- aDataToSend = [MimeUtility encodeQuotedPrintable: aDataToSend
- lineLength: 72
- inHeader: NO];
- }
- else if ( [self contentTransferEncoding] == BASE64 )
- {
- aDataToSend = [MimeUtility encodeBase64: aDataToSend
- lineLength: 72];
- }
- else
- {
- aDataToSend = aDataToSend;
- }
-
- //
- // We now separate our content using the line feed as the delimiter
- // and we rebuild it using the right delimiter.
- //
- anArray = [aDataToSend componentsSeparatedByCString: "\n"];
-
- for (i = 0; i < [anArray count]; i++)
- {
- if ( i == [anArray count] - 1
- && [[anArray objectAtIndex: i] length] == 0 )
- {
- break;
- }
-
- [aMutableData appendData: [anArray objectAtIndex: i]];
- [aMutableData appendBytes: lineTerminator length: strlen(lineTerminator)];
- }
-
- return AUTORELEASE(aMutableData);
- }
-
-
- //
- //
- //
- - (NSData *) boundary
- {
- return boundary;
- }
-
- - (void) setBoundary: (NSData *) theBoundary
- {
- RETAIN(theBoundary);
- RELEASE(boundary);
- boundary = theBoundary;
- }
-
-
- //
- //
- //
- - (NSData *) protocol
- {
- return protocol;
- }
-
- - (void) setProtocol: (NSData *) theProtocol
- {
- RETAIN(theProtocol);
- RELEASE(protocol);
- protocol = theProtocol;
- }
-
-
- //
- //
- //
- - (NSString *) charset
- {
- return charset;
- }
-
- - (void) setCharset: (NSString *) theCharset
- {
- RETAIN(theCharset);
- RELEASE(charset);
- charset = theCharset;
- }
-
-
- //
- //
- //
- - (NSString *) defaultCharset
- {
- return defaultCharset;
- }
-
-
- //
- //
- //
- - (void) setDefaultCharset: (NSString *) theCharset
- {
- if ( theCharset )
- {
- RETAIN(theCharset);
- RELEASE(defaultCharset);
- defaultCharset = theCharset;
- }
- else
- {
- DESTROY(defaultCharset);
- }
- }
-
- @end
-